using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using CodePlex.Resourceful.Amazon.S3;
using System.Diagnostics;

namespace CodePlex.Resourceful.Test
{
    [TestClass]
    public class BalticTests
    {

      
        [TestMethod]
        public void Baltic_Tests()
        {
            string[] aws = GetKeyAndSecret("AWS");

            S3Connection s3 = new S3Connection(aws[0], aws[1], false);
            s3.Tracer.AddListener(TraceTextWriter.Instance);
            Assert.AreEqual(aws[0], s3.AccessKeyID);


            Func20<IRetryStrategy> retry = delegate
            {
                return null;
            };


            string url = //"http://192.168.10.5:8080";
                        // "http://192.168.10.11:8080";
                        "http://baltic-avenue.appspot.com";

            string[] awsBaltic1 = GetKeyAndSecret("baltic1");
            S3Client balticClient1 = new S3Client(awsBaltic1[0], awsBaltic1[1], new Uri(url), retry);
            S3Connection baltic1 = new S3Connection(balticClient1,RequestStyle.Path);
            baltic1.Tracer.AddListener(TraceTextWriter.Instance);
            Assert.AreEqual(awsBaltic1[0], baltic1.AccessKeyID);

            string[] awsBaltic2 = GetKeyAndSecret("baltic2");
            S3Client balticClient2 = new S3Client(awsBaltic2[0], awsBaltic2[1], new Uri(url), retry);
            S3Connection baltic2 = new S3Connection(balticClient2, RequestStyle.Path);
            baltic2.Tracer.AddListener(TraceTextWriter.Instance);
            Assert.AreEqual(awsBaltic2[0], baltic2.AccessKeyID);

            this.KeyLevel(baltic1);
            this.LongKeys(baltic1);
            this.PublicGets(baltic1);
            this.TestExceptions(baltic1);
            this.CommonPrefix(baltic1);
            this.ObjectACL(baltic1);
            this.BucketACL(baltic1);
            this.ObjectMetadata(baltic1);
            this.ContentMD5(baltic1);
            this.BucketListing(baltic1);
            this.DualingAccount(baltic1, baltic2);
            this.BucketLevel(baltic1);

            


        }


        private string[] GetKeyAndSecret(string environmentName)
        {
            string aws = Environment.GetEnvironmentVariable(environmentName);
            return aws.Split(':');
        }



        private void PublicGets(S3Connection baltic)
        {
            string publicBucket = "publicbucket";
            Client publicClient = new Client(baltic.RootUri);
            publicClient.Tracer.AddListener(Console.Out);

            try
            {
                this.DeleteBucketAndContents(baltic, publicBucket);

                ClientResponse response;
                NameValuePairs nvp;
                
                // GET service - never allowed
                response = publicClient.GetRepresentation("");
                Assert.AreEqual(HttpStatus.Forbidden, response.HttpStatus);


                // GET bucket - allowed to list
                baltic.CreateBucket(publicBucket);
                response = publicClient.GetRepresentation(publicBucket);
                Assert.AreEqual(HttpStatus.Forbidden, response.HttpStatus);

                ACL acl = baltic.GetACL(publicBucket);
                acl.Add(S3Constants.Principals.AllUsers, S3Constants.Permissions.Read);
                baltic.SetACL(publicBucket, acl);

                response = publicClient.GetRepresentation(publicBucket);
                Assert.AreEqual(HttpStatus.OK, response.HttpStatus);

                
                // GET object - allowed to read
                nvp = new NameValuePairs();
                nvp[S3Constants.Headers.CannedAccessPolicy] = S3Constants.CannedAccessPolicies.Private;
                baltic.PutObjectAsBytes(publicBucket, "asdf", nvp, null, Encoding.UTF8.GetBytes("asdf"));
                response = publicClient.GetRepresentation(publicBucket + "/asdf");
                Assert.AreEqual(HttpStatus.Forbidden, response.HttpStatus);

                nvp = new NameValuePairs();
                nvp[S3Constants.Headers.CannedAccessPolicy] = S3Constants.CannedAccessPolicies.PublicRead;
                baltic.PutObjectAsBytes(publicBucket, "asdf", nvp, null, Encoding.UTF8.GetBytes("asdf"));
                response = publicClient.GetRepresentation(publicBucket + "/asdf");
                Assert.AreEqual(HttpStatus.OK, response.HttpStatus);
                Assert.AreEqual("asdf", response.AsString());

                nvp = new NameValuePairs();
                nvp[S3Constants.Headers.CannedAccessPolicy] = S3Constants.CannedAccessPolicies.PublicReadWrite;
                baltic.PutObjectAsBytes(publicBucket, "asdf", nvp, null, Encoding.UTF8.GetBytes("asdf"));
                response = publicClient.GetRepresentation(publicBucket + "/asdf");
                Assert.AreEqual(HttpStatus.OK, response.HttpStatus);
                Assert.AreEqual("asdf", response.AsString());



            }
            finally
            {
                this.DeleteBucketAndContents(baltic, publicBucket);
            }
            
            


        }

        private void CommonPrefix(S3Connection baltic)
        {
            string bucket = "cptest";
            DeleteBucketAndContents(baltic, bucket);

            try
            {
                baltic.CreateBucket(bucket);

                ListBucketResult rt;
                rt = baltic.ListBucket(bucket, "", "/");
                Assert.AreEqual(0, rt.CommonPrefixes.Count);


                baltic.PutObjectAsString(bucket, "a/", "");
                rt = baltic.ListBucket(bucket, "", "/");
                Assert.AreEqual(1, rt.CommonPrefixes.Count);
                Assert.AreEqual("a/", rt.CommonPrefixes[0]);

                baltic.PutObjectAsString(bucket, "a/a", "");
                rt = baltic.ListBucket(bucket, "", "/");
                Assert.AreEqual(1, rt.CommonPrefixes.Count);
                Assert.AreEqual("a/", rt.CommonPrefixes[0]);

                baltic.DeleteObject(bucket, "a/a");
                baltic.DeleteObject(bucket, "a/");
                rt = baltic.ListBucket(bucket, "", "/");
                Assert.AreEqual(0, rt.CommonPrefixes.Count);


                baltic.PutObjectAsString(bucket, "b/b/b/b/b", "");
                rt = baltic.ListBucket(bucket, "b", "/");
                Assert.AreEqual(1, rt.CommonPrefixes.Count);
                Assert.AreEqual("b/", rt.CommonPrefixes[0]);
                rt = baltic.ListBucket(bucket, "b/", "/");
                Assert.AreEqual(1, rt.CommonPrefixes.Count);
                Assert.AreEqual("b/b/", rt.CommonPrefixes[0]);

                baltic.DeleteObject(bucket, "b/b/b/b/b");
                rt = baltic.ListBucket(bucket, "", "/");
                Assert.AreEqual(0, rt.CommonPrefixes.Count);
            

            }
            finally
            {
                DeleteBucketAndContents(baltic, bucket);
            }

        }

        private void ObjectACL(S3Connection baltic)
        {
            string bucket = "testobjectacl";
            DeleteBucketAndContents(baltic, bucket);

            try
            {
                baltic.CreateBucket(bucket);

                baltic.PutObjectAsString(bucket, "a", "a");

                // check default
                ACL objectACL = baltic.GetACL(bucket,"a");
                Assert.AreEqual(baltic.Owner, objectACL.Owner);
                Assert.AreEqual(1, objectACL.Principals.Count);
                Assert.AreEqual(1, objectACL.GetPermissions(objectACL.Owner).Count);
                Assert.AreEqual(S3Constants.Permissions.FullControl, objectACL.GetPermissions(objectACL.Owner)[0]);

                baltic.GetObjectAsString(bucket, "a");

                // check fallback
                baltic.SetACL(bucket,"a", baltic.NewACL());
                objectACL = baltic.GetACL(bucket,"a");
                Assert.AreEqual(baltic.Owner, objectACL.Owner);
                Assert.AreEqual(0, objectACL.Principals.Count);

                this.AssertException("denied", delegate
                {
                    baltic.GetObjectAsString(bucket, "a");
                });
                this.AssertException("forbidden", delegate
                {
                    baltic.GetObjectHeaders(bucket, "a");
                });
                // this is s3's behavior!
                baltic.PutObjectAsString(bucket, "a", "a");
                baltic.SetACL(bucket, "a", baltic.NewACL());
                baltic.DeleteObject(bucket, "a");
                baltic.PutObjectAsString(bucket, "a", "a");
                baltic.SetACL(bucket, "a", baltic.NewACL());



                // object read
                objectACL.Add(baltic.Owner, S3Constants.Permissions.Read);
                baltic.SetACL(bucket,"a", objectACL);
              

                baltic.GetObjectAsString(bucket, "a");
                baltic.GetObjectHeaders(bucket, "a");

                // this is s3's behavior!
                baltic.PutObjectAsString(bucket, "a", "a");
                baltic.SetACL(bucket, "a", baltic.NewACL());
                baltic.DeleteObject(bucket, "a");




            }
            finally
            {
                DeleteBucketAndContents(baltic, bucket);
            }
        }

        private void BucketACL(S3Connection baltic)
        {
            string bucket = "testbucketacl";
            DeleteBucketAndContents(baltic, bucket);

            try
            {
                baltic.CreateBucket(bucket);

                baltic.PutObjectAsString(bucket, "a", "a");

                // check default
                ACL bucketACL = baltic.GetACL(bucket);
                Assert.AreEqual(baltic.Owner, bucketACL.Owner);
                Assert.AreEqual(1, bucketACL.Principals.Count);
                Assert.AreEqual(1, bucketACL.GetPermissions(bucketACL.Owner).Count);
                Assert.AreEqual(S3Constants.Permissions.FullControl, bucketACL.GetPermissions(bucketACL.Owner)[0]);

                baltic.ListBucket(bucket);

                // check fallback
                baltic.SetACL(bucket, baltic.NewACL());
                bucketACL = baltic.GetACL(bucket);
                Assert.AreEqual(baltic.Owner, bucketACL.Owner);
                Assert.AreEqual(0, bucketACL.Principals.Count);

                this.AssertException("denied", delegate
                {
                    baltic.ListBucket(bucket);
                });

                // bucket read
                bucketACL.Add(baltic.Owner, S3Constants.Permissions.Read);
                baltic.SetACL(bucket, bucketACL);
                baltic.ListBucket(bucket);

                this.AssertException("denied", delegate
                {
                    baltic.PutObjectAsString(bucket, "a", "a");
                });

                this.AssertException("denied", delegate
                {
                    baltic.DeleteObject(bucket, "a");
                });

                // full control
                bucketACL = baltic.NewACL();
                bucketACL.Add(baltic.Owner, S3Constants.Permissions.FullControl);
                baltic.SetACL(bucket, bucketACL);
                baltic.ListBucket(bucket);
                baltic.PutObjectAsString(bucket, "a", "a");
                baltic.DeleteObject(bucket, "a");


    
               


            }
            finally
            {
                DeleteBucketAndContents(baltic, bucket);
            }
        }

        private void AssertException(string fragment, Action20 action)
        {
            try
            {
                action();
                Assert.Fail("Expected exception, but none thrown.");
            }
            catch (Exception ex)
            {
                if (!ex.Message.ToLower().Contains(fragment))
                {
                    throw;
                }
            }
        }

        private void ContentMD5(S3Connection baltic)
        {
            string bucket = "digest";
            DeleteBucketAndContents(baltic, bucket);

            try
            {
                baltic.CreateBucket(bucket);
                byte[] contents = new byte[0];
                NameValuePairs nvp = new NameValuePairs();
                nvp[HttpHeader.ContentMD5] = Utils.ToBase64(Utils.ToMD5(contents));
                baltic.PutObjectAsBytes(bucket, "key1", nvp, null, contents);

              
            }
            finally
            {
                DeleteBucketAndContents(baltic, bucket);
            }
        }


        private void BucketListing(S3Connection baltic)
        {
            string bucket = "blist";
            DeleteBucketAndContents(baltic, bucket);

            try
            {
                baltic.CreateBucket(bucket);

                baltic.PutObjectAsString(bucket, "b", "b");
                baltic.PutObjectAsString(bucket, "a", "a");

                ReadOnlyList<Key, string> keys = baltic.ListBucket(bucket);
                Assert.AreEqual(2, keys.Count);
                Assert.AreEqual("a", keys[0].Name);
                Assert.AreEqual("b", keys[1].Name);







            }
            finally
            {
                DeleteBucketAndContents(baltic, bucket);
            }
        }


        private void LongKeysSingle(S3Connection baltic, string bucket, string keyName)
        {

            Assert.IsFalse(baltic.IsObjectPresent(bucket, keyName));

            baltic.PutObjectAsString(bucket, keyName, "contents");

            Assert.IsTrue(baltic.IsObjectPresent(bucket, keyName));
            Assert.IsTrue(baltic.ListBucket(bucket).Exists(keyName));

            NameValuePairs nvp = baltic.GetObjectHeaders(bucket, keyName);

            baltic.DeleteObject(bucket, keyName);

            Assert.IsFalse(baltic.IsObjectPresent(bucket, keyName));
            Assert.IsFalse(baltic.ListBucket(bucket).Exists(keyName));

        }

        private void LongKeys(S3Connection baltic)
        {
            string bucket = "testlongkeys";
            DeleteBucketAndContents(baltic, bucket);

           
            try
            {
                baltic.CreateBucket(bucket);

                LongKeysSingle(baltic, bucket, new string('x',5));

                LongKeysSingle(baltic, bucket, new string('x', 512));

                LongKeysSingle(baltic, bucket, new string('x', 500));
                LongKeysSingle(baltic, bucket, new string('x', 1001));
                LongKeysSingle(baltic, bucket, new string('x', 1024));

            }
            finally
            {
               DeleteBucketAndContents(baltic, bucket);
            }

        }

        private void ObjectMetadata(S3Connection baltic)
        {
            DeleteBucketAndContents(baltic, "om");

            try
            {
                baltic.CreateBucket("om");

                NameValuePairs nvp = new NameValuePairs();
                nvp[HttpHeader.ContentType] = MediaType.TextPlain;
                nvp[HttpHeader.CacheControl] = "x";
                nvp[S3Constants.Headers.ContentDisposition] = "whatever";
                nvp[HttpHeader.ContentEncoding] = "asdf";
                nvp[HttpHeader.Expires] = "sometime";
                nvp[S3Constants.Headers.AmazonUserMetadataPrefix + "foo"] = "bar";
                baltic.PutObjectAsBytes("om", "key1", nvp, null, new byte[0]);

                nvp = baltic.GetObjectHeaders("om", "key1");
                Assert.AreEqual(MediaType.TextPlain, nvp[HttpHeader.ContentType]);
                Assert.AreEqual("x", nvp[HttpHeader.CacheControl]);
                Assert.AreEqual("whatever", nvp[S3Constants.Headers.ContentDisposition]);
               // Assert.AreEqual("asdf", nvp[HttpHeader.ContentEncoding]);
                Assert.AreEqual("sometime", nvp[HttpHeader.Expires]);
                Assert.AreEqual("bar", nvp[S3Constants.Headers.AmazonUserMetadataPrefix + "foo"]);
            }
            finally
            {
                DeleteBucketAndContents(baltic, "om");
            }
        }

        private void DeleteBucketIfExists(S3Connection cn, string bucket)
        {

            if (!cn.IsBucketAvailable(bucket))
            {
                cn.DeleteBucket(bucket);
            }
           
        }
        private void DualingAccount(S3Connection baltic1, S3Connection baltic2)
        {
            DeleteBucketIfExists(baltic1, "u1");
            DeleteBucketIfExists(baltic2, "u2");
            

            try
            {
                Assert.IsTrue(baltic1.IsBucketAvailable("u2"));
                Assert.IsTrue(baltic2.IsBucketAvailable("u1"));


                Assert.IsFalse(baltic1.ListBuckets().Buckets.Exists("u1"));
                baltic1.CreateBucket("u1");
                Assert.IsTrue(baltic1.ListBuckets().Buckets.Exists("u1"));

                Assert.IsFalse(baltic2.ListBuckets().Buckets.Exists("u2"));
                baltic2.CreateBucket("u2");
                Assert.IsTrue(baltic2.ListBuckets().Buckets.Exists("u2"));


                Assert.IsFalse(baltic1.ListBuckets().Buckets.Exists("u2"));
                Assert.IsFalse(baltic2.ListBuckets().Buckets.Exists("u1"));

                Assert.IsFalse(baltic1.IsBucketAvailable("u2"));
                Assert.IsFalse(baltic2.IsBucketAvailable("u1"));

            }
            finally
            {
                DeleteBucketIfExists(baltic1, "u1");
                DeleteBucketIfExists(baltic2, "u2");
            }


        }


        private void TestExceptions(S3Connection baltic)
        {
            string bucket = "testexceptions";

            this.DeleteBucketAndContents(baltic, bucket);

            baltic.CreateBucket(bucket);
            baltic.PutObjectAsString(bucket, "key1", "text1");

            try
            {
                baltic.DeleteBucket(bucket);
                Assert.Fail("Expected exception");

            }
            catch (Exception ex)
            {
                S3Exception s3c = ex as S3Exception;
               
            }
            baltic.DeleteObject(bucket, "key1");
            baltic.DeleteBucket(bucket);



            baltic.CreateBucket(bucket);
            try
            {
                try
                {
                    baltic.PutObjectAsString(bucket, new string('x', 1025), "asdf");
                    Assert.Fail("Expected exception");
                }
                catch { }
            }
            finally
            {
                this.DeleteBucketAndContents(baltic, bucket);
            }



        }


        private void KeyLevel(S3Connection baltic)
        {
            DeleteBucketAndContents(baltic, "keylevel");

            try
            {
                baltic.CreateBucket("keylevel");

                Assert.AreEqual(0, baltic.ListBucket("keylevel").Count);
                Assert.IsFalse(baltic.IsObjectPresent("keylevel", "key1"));

                baltic.PutObjectAsString("keylevel", "key1", "text1");

                Assert.IsTrue(baltic.IsObjectPresent("keylevel", "key1"));

                ReadOnlyList<Key, string> keys = baltic.ListBucket("keylevel");
                Assert.AreEqual(1, keys.Count);

                Assert.AreEqual("key1", keys[0].Name);
                Assert.AreEqual(5L, keys[0].Size);
                Assert.AreEqual(@"""" + Utils.ToBase16(Utils.ToMD5(Encoding.UTF8.GetBytes("text1"))) + @"""", keys[0].Etag);

                Assert.AreEqual("text1", baltic.GetObjectAsString("keylevel", "key1"));


                baltic.DeleteObject("keylevel", "key1");
                Assert.AreEqual(0, baltic.ListBucket("keylevel").Count);





                Random rnd = new Random();
                byte[] bytes = new byte[1024*1024/2];
                rnd.NextBytes(bytes);

                baltic.PutObjectAsBytes("keylevel", "key2", null, null, bytes);
                ReadOnlyList<Key, string> keys2 = baltic.ListBucket("keylevel");
                Assert.AreEqual(1, keys2.Count);
                Assert.AreEqual(bytes.LongLength, keys2[0].Size);
                Assert.AreEqual(@"""" + Utils.ToBase16(Utils.ToMD5(bytes)) + @"""", keys2[0].Etag);

                byte[] bytes2 = baltic.GetObjectAsBytes("keylevel", "key2");
      

                Assert.AreEqual(bytes.Length, bytes2.Length);
                for (int i = 0; i < bytes.Length; i++)
                {
                    Assert.AreEqual(bytes[i], bytes2[i]);
                }
                baltic.DeleteObject("keylevel", "key2");
                Assert.AreEqual(0, baltic.ListBucket("keylevel").Count);

            }
            finally
            {
                DeleteBucketAndContents(baltic, "keylevel");
            }

        }

        private void DeleteBucketAndContents(S3Connection cn, string bucket)
        {
            if (!cn.IsBucketAvailable(bucket))
            {
                ACL acl = cn.NewACL();
                acl.Add(cn.Owner, S3Constants.Permissions.FullControl);
                cn.SetACL(bucket, acl);

                foreach (Key k in cn.ListBucket(bucket))
                {
                    cn.DeleteObject(bucket, k.Name);
                }
                cn.DeleteBucket(bucket);
            }
            
        }


        private void BucketLevel(S3Connection baltic)
        {
            DeleteBucketIfExists(baltic, "bucketlevel");

            Assert.IsFalse(baltic.ListBuckets().Buckets.Exists("bucketlevel"));

            baltic.CreateBucket("bucketlevel");

            Assert.IsTrue(baltic.ListBuckets().Buckets.Exists("bucketlevel"));

            baltic.DeleteBucket("bucketlevel");

            Assert.IsFalse(baltic.ListBuckets().Buckets.Exists("bucketlevel"));
        }




    }
}
